package io.guangli.tutorials1v1vcall;

import static io.agora.rtc.Constants.VIDEO_MIRROR_MODE_DISABLED;
import static io.agora.rtc.video.VideoCanvas.RENDER_MODE_HIDDEN;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.blankj.utilcode.util.LogUtils;
import com.google.gson.Gson;
import com.herewhite.sdk.AbstractRoomCallbacks;
import com.herewhite.sdk.CommonCallback;
import com.herewhite.sdk.Converter;
import com.herewhite.sdk.ConverterCallbacks;
import com.herewhite.sdk.Room;
import com.herewhite.sdk.RoomCallbacks;
import com.herewhite.sdk.RoomParams;
import com.herewhite.sdk.WhiteSdk;
import com.herewhite.sdk.WhiteSdkConfiguration;
import com.herewhite.sdk.WhiteboardView;
import com.herewhite.sdk.converter.ConvertType;
import com.herewhite.sdk.converter.ConverterV5;
import com.herewhite.sdk.converter.ImageFormat;
import com.herewhite.sdk.domain.AkkoEvent;
import com.herewhite.sdk.domain.AnimationMode;
import com.herewhite.sdk.domain.Appliance;
import com.herewhite.sdk.domain.BroadcastState;
import com.herewhite.sdk.domain.CameraBound;
import com.herewhite.sdk.domain.CameraConfig;
import com.herewhite.sdk.domain.ContentModeConfig;
import com.herewhite.sdk.domain.ConversionInfo;
import com.herewhite.sdk.domain.ConvertException;
import com.herewhite.sdk.domain.ConvertedFiles;
import com.herewhite.sdk.domain.FontFace;
import com.herewhite.sdk.domain.GlobalState;
import com.herewhite.sdk.domain.ImageInformationWithUrl;
import com.herewhite.sdk.domain.MemberState;
import com.herewhite.sdk.domain.Point;
import com.herewhite.sdk.domain.PptPage;
import com.herewhite.sdk.domain.Promise;
import com.herewhite.sdk.domain.RectangleConfig;
import com.herewhite.sdk.domain.Region;
import com.herewhite.sdk.domain.RoomPhase;
import com.herewhite.sdk.domain.RoomState;
import com.herewhite.sdk.domain.SDKError;
import com.herewhite.sdk.domain.Scene;
import com.herewhite.sdk.domain.ViewMode;
import com.herewhite.sdk.domain.WhiteDisplayerState;

import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.models.ChannelMediaOptions;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
import io.guangli.tutorials1v1vcall.httpcomponent.OkGoStringCallBack;

public class VideoChatViewActivity extends AppCompatActivity {
    private static final String TAG = VideoChatViewActivity.class.getSimpleName();

    private static final int PERMISSION_REQ_ID = 22;

    private static final String EVENT_NAME = "WhiteCommandCustomEvent";
    final String SCENE_DIR = "/dir";
    // Permission WRITE_EXTERNAL_STORAGE is not mandatory
    // for Agora RTC SDK, just in case if you wanna save
    // logs to external sdcard.
    private static final String[] REQUESTED_PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.CAMERA
    };

    private RtcEngine mRtcEngine;
    private boolean mCallEnd;
    private boolean mMuted;

    private LinearLayout mLocalContainer;
    private RelativeLayout mRemoteContainer;
    private VideoCanvas mLocalVideo;
    private Map<Integer, VideoCanvas> mRemoteVideos = new HashMap<>();

    private ImageView mCallBtn;
    private ImageView mMuteBtn;
    private ImageView mSwitchCameraBtn;
    private String channelName;
    private String token = "";
    private int index = 0;
    private int clickIndex = 0;
    // Customized logger view
//    private LoggerRecyclerView mLogView;


    // 你的互动白板 App Identifier
    String appId = "MjudQGL1EeyoL01vk-NuuQ/ot4GiMLtW3vRcw";
    // 你的房间 UUID
    String uuid = "7c377e009f5911ecb76503cfe4f9ad58";
    // 用户唯一标识符。如果你使用 2.15.0 之前版本的 SDK，不需要添加该行
    String uid = System.currentTimeMillis() + "";
    // 你的 Room Token
    String roomToken = "NETLESSROOM_YWs9VlNOOU1xbUdFZVZkWHZTMCZub25jZT0xNjQ2ODExNTI5NTUyMDAmcm9sZT0wJnNpZz00M2U4MDgxMjI4OTQ1ZGEzOGZjZjljYjMwMTNjZjZmNzk5OTk2NTUzZTMxYWUyMmMzYzZlNzMzNDM2ZjNkNzFmJnV1aWQ9N2MzNzdlMDA5ZjU5MTFlY2I3NjUwM2NmZTRmOWFkNTg";

    int[] color = new int[]{255, 0, 0};
    // 创建 WhiteboardView 对象，实现白板 view
    WhiteboardView mWhiteboardView;
    @VisibleForTesting
    WhiteSdk mWhiteSdk;
    @VisibleForTesting
    Room mRoom;
    @VisibleForTesting
    RoomCallbacks mRoomCallbackHock = new AbstractRoomCallbacks() {
    };
    final Gson gson = new Gson();
    /**
     * Event handler registered into RTC engine for RTC callbacks.
     * Note that UI operations needs to be in UI thread because RTC
     * engine deals with the events in a separate thread.
     */
    private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
        @Override
        public void onError(int err) {
            super.onError(err);
            LogUtils.e(err);
        }

        /**
         * Occurs when the local user joins a specified channel.
         * The channel name assignment is based on channelName specified in the joinChannel method.
         * If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
         *
         * @param channel Channel name.
         * @param uid User ID.
         * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered.
         */
        @Override
        public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "Join channel success, uid: " + (uid & 0xFFFFFFFFL));
//                    mLogView.logI("Join channel success, uid: " + (uid & 0xFFFFFFFFL));
                    mRtcEngine.muteAllRemoteVideoStreams(false);
                }
            });
        }

        @Override
        public void onUserJoined(final int uid, int elapsed) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "First remote video decoded, uid: " + (uid & 0xFFFFFFFFL));
//                    mLogView.logI("First remote video decoded, uid: " + (uid & 0xFFFFFFFFL));
                    setupRemoteVideo(uid);
                }
            });
        }

        /**
         * Occurs when a remote user (Communication)/host (Live Broadcast) leaves the channel.
         *
         * There are two reasons for users to become offline:
         *
         *     Leave the channel: When the user/host leaves the channel, the user/host sends a
         *     goodbye message. When this message is received, the SDK determines that the
         *     user/host leaves the channel.
         *
         *     Drop offline: When no data packet of the user or host is received for a certain
         *     period of time (20 seconds for the communication profile, and more for the live
         *     broadcast profile), the SDK assumes that the user/host drops offline. A poor
         *     network connection may lead to false detections, so we recommend using the
         *     Agora RTM SDK for reliable offline detection.
         *
         * @param uid ID of the user or host who leaves the channel or goes offline.
         * @param reason Reason why the user goes offline:
         *
         *     USER_OFFLINE_QUIT(0): The user left the current channel.
         *     USER_OFFLINE_DROPPED(1): The SDK timed out and the user dropped offline because no data packet was received within a certain period of time. If a user quits the call and the message is not passed to the SDK (due to an unreliable channel), the SDK assumes the user dropped offline.
         *     USER_OFFLINE_BECOME_AUDIENCE(2): (Live broadcast only.) The client role switched from the host to the audience.
         */
        @Override
        public void onUserOffline(final int uid, int reason) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "User offline, uid: " + (uid & 0xFFFFFFFFL));
//                    mLogView.logI("User offline, uid: " + (uid & 0xFFFFFFFFL));
                    onRemoteUserLeft(uid);
                }
            });
        }
    };


    private void setupRemoteVideo(int uid) {
        ViewGroup parent = mLocalContainer;
//        if (parent.indexOfChild(mLocalVideo.view) > -1) {
//            parent = mRemoteContainer;
//        }

        // Only one remote video view is available for this
        // tutorial. Here we check if there exists a surface
        // view tagged as this uid.
//        if (mRemoteVideo != null) {
//            return;
//        }

        /*
          Creates the video renderer view.
          CreateRendererView returns the SurfaceView type. The operation and layout of the view
          are managed by the app, and the Agora SDK renders the view provided by the app.
          The video display view must be created using this method instead of directly
          calling SurfaceView.
         */
        TextureView view = RtcEngine.CreateTextureView(getBaseContext());
//        view.setZOrderMediaOverlay(parent == mLocalContainer);

        index++;
        ViewGroup.LayoutParams l = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (130f * Resources.getSystem().getDisplayMetrics().density + 0.5f));
        view.setLayoutParams(l);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                clickVideo(v);
            }
        });
        parent.addView(view, index - 1);
        VideoCanvas mRemoteVideo = new VideoCanvas(view, RENDER_MODE_HIDDEN, uid);
        // Initializes the video view of a remote user.
        mRtcEngine.setupRemoteVideo(mRemoteVideo);

        int resul = mRtcEngine.setRemoteRenderMode(uid, RENDER_MODE_HIDDEN, VIDEO_MIRROR_MODE_DISABLED);

        mRemoteVideos.put(index, mRemoteVideo);
        LogUtils.e("resul=" + resul + "---" + index + "-----" + mRemoteVideos.toString());
    }

    private void clickVideo(View view) {
        for (Integer integer : mRemoteVideos.keySet()) {
            VideoCanvas mRemoteVideo = mRemoteVideos.get(integer);
            if (mRemoteVideo.view == view) {
                clickIndex = integer;
                LogUtils.e("clickIndex = " + clickIndex);
                switchView(mRemoteVideo, mLocalVideo);
//                        mRemoteVideos.remove(integer);
            }
        }
    }

    private void onRemoteUserLeft(int uid) {
        int delIndex = -100;
        for (Integer integer : mRemoteVideos.keySet()) {
            VideoCanvas mRemoteVideo = mRemoteVideos.get(integer);
            if (mRemoteVideo != null && mRemoteVideo.uid == uid) {
                delIndex = integer;
                removeFromParent(mRemoteVideo, true);

                // Destroys remote view
                mRemoteVideo = null;
            }
        }
        if (mLocalVideo != null && mLocalVideo.uid == uid) {
            removeFromParent(mLocalVideo, false);
        }
        if (delIndex != -100) {
            mRemoteVideos.remove(delIndex);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));//ColorUtils.getColor(R.color.color_060E17)
        getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        setContentView(R.layout.activity_video_chat_view);
        mRemoteVideos.clear();
        channelName = getIntent().getStringExtra("channelName");
        token = getIntent().getStringExtra("token");
        uuid = getIntent().getStringExtra("roomUuid");
        com.blankj.utilcode.util.LogUtils.e(token, channelName);
        initUI();

//        // Ask for permissions at runtime.
//        // This is just an example set of permissions. Other permissions
//        // may be needed, and please refer to our online documents.
        if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
                checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
            initEngineAndJoinChannel();
        }
        initWhiteboard();
    }

    private void initUI() {
        mWhiteboardView = findViewById(R.id.white);

        mLocalContainer = findViewById(R.id.local_video_view_container);
        mRemoteContainer = findViewById(R.id.remote_video_view_container);

        mCallBtn = findViewById(R.id.btn_call);
        mMuteBtn = findViewById(R.id.btn_mute);
        mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);

//        mLogView = findViewById(R.id.log_recycler_view);

        // Sample logs are optional.
        showSampleLogs();
    }

    private void showSampleLogs() {
//        mLogView.logI("Welcome to Agora 1v1 video call");
//        mLogView.logW("You will see custom logs here");
//        mLogView.logE("You can also use this to show errors");
    }

    private boolean checkSelfPermission(String permission, int requestCode) {
        if (ContextCompat.checkSelfPermission(this, permission) !=
                PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
            return false;
        }

        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQ_ID) {
            if (grantResults.length < 2 || grantResults[0] != PackageManager.PERMISSION_GRANTED ||
                    grantResults[1] != PackageManager.PERMISSION_GRANTED) {
                showLongToast("Need permissions " + Manifest.permission.RECORD_AUDIO +
                        "/" + Manifest.permission.CAMERA);
                finish();
                return;
            }

            // Here we continue only if all permissions are granted.
            // The permissions can also be granted in the system settings manually.
            initEngineAndJoinChannel();
        }
    }

    private void showLongToast(final String msg) {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
            }
        });
    }

    private void initEngineAndJoinChannel() {
        // This is our usual steps for joining
        // a channel and starting a call.
        initializeEngine();
        setupVideoConfig();
        setupLocalVideo();
        joinChannel();
    }

    private void initializeEngine() {
        try {
            mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);

        } catch (Exception e) {
            LogUtils.e(Log.getStackTraceString(e));
            throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
        }
    }

    private void setupVideoConfig() {
        // In simple use cases, we only need to enable video capturing
        // and rendering once at the initialization step.
        // Note: audio recording and playing is enabled by default.
        mRtcEngine.enableVideo();

        // Please go to this page for detailed explanation
        // https://docs.agora.io/en/Video/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#af5f4de754e2c1f493096641c5c5c1d8f
        mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
                VideoEncoderConfiguration.VD_640x360,
                VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
                VideoEncoderConfiguration.STANDARD_BITRATE,
                VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));

        mRtcEngine.setParameters("{\"che.video.lowBitRateStreamParameter\":{\"width\":320,\"height\":180,\"frameRate\":5,\"bitRate\":140}}");
    }

    private void setupLocalVideo() {
        // This is used to set a local preview.
        // The steps setting local and remote view are very similar.
        // But note that if the local user do not have a uid or do
        // not care what the uid is, he can set his uid as ZERO.
        // Our server will assign one and return the uid via the event
        // handler callback function (onJoinChannelSuccess) after
        // joining the channel successfully.
        TextureView view = RtcEngine.CreateTextureView(getBaseContext());
//        view.setZOrderMediaOverlay(true);
        mRemoteContainer.addView(view);
        // Initializes the local video view.
        // RENDER_MODE_HIDDEN: Uniformly scale the video until it fills the visible boundaries. One dimension of the video may have clipped contents.
        mLocalVideo = new VideoCanvas(view, RENDER_MODE_HIDDEN, 0);
        mRtcEngine.setupLocalVideo(mLocalVideo);
    }

    private void joinChannel() {
        // 1. Users can only see each other after they join the
        // same channel successfully using the same app id.
        // 2. One token is only valid for the channel name that
        // you use to generate this token.
//        String token = getString(R.string.agora_access_token);
//        String token = to;
        if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
            LogUtils.e("default, no token");
            token = null; // default, no token
        }
        mRtcEngine.joinChannel(token, channelName, "Extra Optional Data", 0, new ChannelMediaOptions());
    }

    @Override
    protected void onDestroy() {

        if (!mCallEnd) {
            leaveChannel();
        }
        /*
          Destroys the RtcEngine instance and releases all resources used by the Agora SDK.

          This method is useful for apps that occasionally make voice or video calls,
          to free up resources for other operations when not making calls.
         */
        RtcEngine.destroy();
        super.onDestroy();
    }

    private void leaveChannel() {
        mRtcEngine.leaveChannel();
    }

    public void onLocalAudioMuteClicked(View view) {
        mMuted = !mMuted;
        setAudio();
    }

    private void setAudio() {
        // Stops/Resumes sending the local audio stream.
        mRtcEngine.muteLocalAudioStream(mMuted);
        int res = mMuted ? R.drawable.btn_mute : R.drawable.btn_unmute;
        mMuteBtn.setImageResource(res);
    }

    public void onSwitchCameraClicked(View view) {
        // Switches between front and rear cameras.
        mRtcEngine.switchCamera();
    }

    public void onCallClicked(View view) {
        if (mCallEnd) {
            startCall();
            mCallEnd = false;
            mCallBtn.setImageResource(R.drawable.btn_endcall);
        } else {
            endCall();
            mCallEnd = true;
            mCallBtn.setImageResource(R.drawable.btn_startcall);
        }
        showButtons(!mCallEnd);
    }

    private void startCall() {
        setupLocalVideo();
        joinChannel();
        setAudio();
    }

    private void endCall() {
        removeFromParent(mLocalVideo, false);
        mLocalVideo = null;
        for (Integer integer : mRemoteVideos.keySet()) {
            VideoCanvas mRemoteVideo = mRemoteVideos.get(integer);
            removeFromParent(mRemoteVideo, true);
        }
        mRemoteVideos.clear();
        mRemoteContainer.removeAllViews();
        mLocalContainer.removeAllViews();
        index = 0;
        leaveChannel();
    }

    private void showButtons(boolean show) {
        int visibility = show ? View.VISIBLE : View.GONE;
        mMuteBtn.setVisibility(visibility);
        mSwitchCameraBtn.setVisibility(visibility);
    }

    private ViewGroup removeFromParent(VideoCanvas canvas, boolean isRemove) {
        if (canvas != null) {
            ViewParent parent = canvas.view.getParent();
            if (parent != null) {
                if (isRemove)
                    index--;
                ViewGroup group = (ViewGroup) parent;
                group.removeView(canvas.view);
                return group;
            }
        }
        return null;
    }

    private void switchView(VideoCanvas canvas, VideoCanvas videoCanvas) {
        ViewGroup parent = removeFromParent(canvas, false);
        ViewGroup viewGroup = removeFromParent(videoCanvas, false);
        if (parent == mLocalContainer) {
            if (canvas.view instanceof TextureView) {
//                ((SurfaceView) canvas.view).setZOrderMediaOverlay(false);
            }
            canvas.view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            mLocalVideo = canvas;
            LogUtils.e("parent == mLocalContainer");
            mRemoteContainer.addView(canvas.view);
        }
        if (viewGroup == mRemoteContainer) {
            if (videoCanvas.view instanceof TextureView) {
//                ((TextureView) canvas.view).setZOrderMediaOverlay(true);
            }
            videoCanvas.view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (130f * Resources.getSystem().getDisplayMetrics().density + 0.5f)));
            videoCanvas.view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    clickVideo(v);
                }
            });
            mRemoteVideos.put(clickIndex, videoCanvas);
            LogUtils.e("parent != mLocalContainer clickIndex=" + clickIndex + mRemoteVideos.toString());
            mLocalContainer.addView(videoCanvas.view, clickIndex - 1);
        }
    }

    public void onLocalContainerClick(View view) {
//        switchView(mLocalVideo);
//        switchView(mRemoteVideos);

//        LinearLayout linearLayout = ((LinearLayout) view);
//        for (Integer integer : mRemoteVideos.keySet()) {
//            VideoCanvas mRemoteVideo = mRemoteVideos.get(integer);
//            if (mRemoteVideo.view == view) {
//                switchView(mRemoteVideo);
//            }
//        }
    }

    private void initWhiteboard() {
        mWhiteboardView.getSettings().setAllowUniversalAccessFromFileURLs(true);

        // 使用 LocalFileWebViewClient 对 动态 ppt 拦截进行替换，先查看本地是否有，如果没有再发出网络请求
        LocalFileWebViewClient client = new LocalFileWebViewClient();
        client.setPptDirectory(getCacheDir().getAbsolutePath());
        mWhiteboardView.setWebViewClient(client);
        mWhiteboardView.setBackgroundColor(Color.TRANSPARENT);


        Util.INSTANCE.buildRoomToken(
                uuid,
                new OkGoStringCallBack<RoomTokenBean>(this, RoomTokenBean.class, false) {
                    @Override
                    public void onSuccess2Bean(@Nullable RoomTokenBean bean) {
                        roomToken = bean.getData();
                        joinRoom(uuid, roomToken);
                    }
                });
    }

    /**
     * 自定义 GlobalState 示例
     * 继承自 GlobalState 的子类，然后调用 {@link WhiteDisplayerState#setCustomGlobalStateClass(Class)}
     */
    class MyGlobalState extends GlobalState {
        public String getOne() {
            return one;
        }

        public void setOne(String one) {
            this.one = one;
        }

        String one;
    }

    //region room
    private void joinRoom(String uuid, String token) {
        logRoomInfo("room uuid: " + uuid + "\nroom token: " + token);

        //存档一下，方便重连
        this.uuid = uuid;
        this.roomToken = token;

        WhiteSdkConfiguration configuration = new WhiteSdkConfiguration(appId, true);
        /*显示用户头像*/
        configuration.setUserCursor(true);
        //动态 ppt 需要的自定义字体，如果没有使用，无需调用
        configuration.setFonts(new MapBuilder<String, String>().put("宋体", "https://your-cdn.com/Songti.ttf").build());

        mWhiteSdk = new WhiteSdk(mWhiteboardView, this, configuration);

        //图片替换 API，需要在 whiteSDKConfig 中先行调用 setHasUrlInterrupterAPI，进行设置，否则不会被回调。
        mWhiteSdk.setCommonCallbacks(new CommonCallback() {
            @Override
            public String urlInterrupter(String sourceUrl) {
                return sourceUrl;
            }

            @Override
            public void onMessage(JSONObject message) {
                Log.d(TAG, message.toString());
            }

            @Override
            public void sdkSetupFail(SDKError error) {
                Log.e(TAG, "sdkSetupFail " + error.toString());
            }

            @Override
            public void throwError(Object args) {
                Log.e(TAG, "throwError " + args);
            }

            @Override
            public void onPPTMediaPlay() {
                logAction();
            }

            @Override
            public void onPPTMediaPause() {
                logAction();
            }

            @Override
            public void onLogger(JSONObject object) {
                logAction(object.toString());
            }
        });

        FontFace fontFace = new FontFace("example", "url(https://white-pan.oss-cn-shanghai.aliyuncs.com/Pacifico-Regular.ttf)");
        // mWhiteSdk.setupFontFaces(new FontFace[]{fontFace});
        mWhiteSdk.loadFontFaces(new FontFace[]{fontFace}, new Promise<JSONObject>() {
            @Override
            public void then(JSONObject object) {
                logRoomInfo("loadFontFaces");
            }

            @Override
            public void catchEx(SDKError t) {

            }
        });

        /** 设置自定义全局状态，在后续回调中 GlobalState 直接进行类型转换即可 */
        WhiteDisplayerState.setCustomGlobalStateClass(MyGlobalState.class);

        //如需支持用户头像，请在设置 WhiteSdkConfiguration 后，再调用 setUserPayload 方法，传入符合用户信息
        RoomParams roomParams = new RoomParams(uuid, token, uid);

        final Date joinDate = new Date();
        logRoomInfo("native join " + joinDate);
        mWhiteSdk.joinRoom(roomParams, new RoomCallbacks() {
            @Override
            public void onCanUndoStepsUpdate(long canUndoSteps) {
                mRoomCallbackHock.onCanUndoStepsUpdate(canUndoSteps);
                logRoomInfo("canUndoSteps: " + canUndoSteps);
            }

            @Override
            public void onCanRedoStepsUpdate(long canRedoSteps) {
                mRoomCallbackHock.onCanRedoStepsUpdate(canRedoSteps);
                logRoomInfo("onCanRedoStepsUpdate: " + canRedoSteps);
            }

            @Override
            public void onCatchErrorWhenAppendFrame(long userId, Exception error) {
                mRoomCallbackHock.onCatchErrorWhenAppendFrame(userId, error);
                logRoomInfo("onCatchErrorWhenAppendFrame: " + userId + " error " + error.getMessage());
            }

            @Override
            public void onPhaseChanged(RoomPhase phase) {
                mRoomCallbackHock.onPhaseChanged(phase);
                //在此处可以处理断连后的重连逻辑
                logRoomInfo("onPhaseChanged: " + phase.name());
                showToast(phase.name());
            }

            @Override
            public void onDisconnectWithError(Exception e) {
                mRoomCallbackHock.onDisconnectWithError(e);
                logRoomInfo("onDisconnectWithError: " + e.getMessage());
            }

            @Override
            public void onKickedWithReason(String reason) {
                mRoomCallbackHock.onKickedWithReason(reason);
                logRoomInfo("onKickedWithReason: " + reason);
            }

            @Override
            public void onRoomStateChanged(RoomState modifyState) {
                mRoomCallbackHock.onRoomStateChanged(modifyState);
                logRoomInfo("onRoomStateChanged:" + gson.toJson(modifyState));
            }
        }, new Promise<Room>() {
            @Override
            public void then(Room room) {
                //记录加入房间消耗的时长
                logRoomInfo("native join in room duration: " + (System.currentTimeMillis() - joinDate.getTime()) / 1000f + "s");
                mRoom = room;
                setPencil();
                addCustomEventListener();
            }

            @Override
            public void catchEx(SDKError t) {
                logRoomInfo("native join fail: " + t.getMessage());
                showToast(t.getMessage());
            }
        });
    }
    //endregion

    private void addCustomEventListener() {
        mRoom.addMagixEventListener(EVENT_NAME, event -> {
            logRoomInfo("customEvent payload: " + event.getPayload().toString());
            showToast(gson.toJson(event.getPayload()));
        });
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Note：sdk内部已经实现size变更。
        // 特别情况下出现页面异常状况，调用WhiteboardView.setAutoResize(false)禁用内部处理; 外部调用在合适时机调用Room.refreshViewSize()
        // logRoomInfo("width:" + mWhiteboardView.getWidth() / getResources().getDisplayMetrics().density + " height: " + mWhiteboardView.getHeight() / getResources().getDisplayMetrics().density);
        // onConfigurationChanged 调用时，横竖屏切换并没有完成，需要延迟调用
        // new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
        //     @Override
        //     public void run() {
        //         mRoom.refreshViewSize();
        //         logRoomInfo("width:" + mWhiteboardView.getWidth() / getResources().getDisplayMetrics().density + " height: " + mWhiteboardView.getHeight() / getResources().getDisplayMetrics().density);
        //     }
        // }, 1000);
    }

    //endregion

    //region menu
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.room_command, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return true;
    }

    private CameraBound customBound(double maxScale) {
        CameraBound bound = new CameraBound();
        bound.setCenterX(0d);
        bound.setCenterY(0d);
        bound.setHeight((double) (mWhiteboardView.getHeight() / this.getResources().getDisplayMetrics().density));
        bound.setWidth((double) (mWhiteboardView.getWidth() / this.getResources().getDisplayMetrics().density));
        ContentModeConfig contentModeConfig = new ContentModeConfig();
        contentModeConfig.setScale(maxScale);
        contentModeConfig.setMode(ContentModeConfig.ScaleMode.CENTER_INSIDE_SCALE);
        bound.setMaxContentMode(contentModeConfig);
        return bound;
    }

    public void scalePptToFit(MenuItem item) {
        mRoom.scalePptToFit(AnimationMode.Continuous);
    }

    public void reconnect(MenuItem item) {
        mRoom.disconnect(new Promise<Object>() {
            @Override
            public void then(Object b) {
                joinRoom(VideoChatViewActivity.this.uuid, VideoChatViewActivity.this.roomToken);
            }

            @Override
            public void catchEx(SDKError t) {

            }
        });
    }

    public void setWritableFalse(MenuItem item) {
        mRoom.setWritable(false, new Promise<Boolean>() {
            @Override
            public void then(Boolean aBoolean) {
                logRoomInfo("room writable: " + aBoolean);
            }

            @Override
            public void catchEx(SDKError t) {

            }
        });
    }

    public void setWritableTrue(MenuItem item) {
        mRoom.setWritable(true, new Promise<Boolean>() {
            @Override
            public void then(Boolean aBoolean) {
                logRoomInfo("room writable: " + aBoolean);
            }

            @Override
            public void catchEx(SDKError t) {

            }
        });
    }

    @SuppressLint("SourceLockedOrientationActivity")
    public void orientation(MenuItem item) {
        if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            VideoChatViewActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        } else {
            VideoChatViewActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }
    }

    public void setBound(MenuItem item) {
        CameraBound bound = customBound(3);
        mRoom.setCameraBound(bound);
    }

    public void nextScene(MenuItem item) {
        int nextIndex = mRoom.getSceneState().getIndex() + 1;
        mRoom.setSceneIndex(nextIndex, new Promise<Boolean>() {
            @Override
            public void then(Boolean result) {

            }

            @Override
            public void catchEx(SDKError t) {

            }
        });
    }

    public void undoRedoOperation(MenuItem item) {
        // 需要开启本地序列化，才能操作 redo undo
        mRoom.disableSerialization(false);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mRoom.undo();
            }
        }, 1000);

    }

    public void redoOperation(MenuItem item) {
        mRoom.disableSerialization(false);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mRoom.redo();
            }
        }, 1000);
    }

    public void duplicate(MenuItem item) {
        mRoom.duplicate();
    }

    public void copyPaste(MenuItem item) {
        mRoom.copy();
        mRoom.paste();
    }

    public void deleteOperation(MenuItem item) {
        mRoom.deleteOperation();
    }

    public void getPreviewImage(MenuItem item) {
        mRoom.getScenePreviewImage("/init", new Promise<Bitmap>() {
            @Override
            public void then(Bitmap bitmap) {
                logAction("get bitmap");
            }

            @Override
            public void catchEx(SDKError t) {
                logAction("get bitmap error");
            }
        });
    }

    public void getSceneImage(MenuItem item) {
        mRoom.getSceneSnapshotImage("/init", new Promise<Bitmap>() {
            @Override
            public void then(Bitmap bitmap) {
                logAction("get bitmap");
            }

            @Override
            public void catchEx(SDKError t) {
                logAction("get bitmap error");
            }
        });
    }
/*
    public void staticConvert(MenuItem item) {
        Converter c = new Converter(this.token);
        c.startConvertTask("https://white-cn-edge-doc-convert.oss-cn-hangzhou.aliyuncs.com/LightWaves.pdf", Converter.ConvertType.Static, new ConverterCallbacks() {
            @Override
            public void onFailure(ConvertException e) {
                logAction(e.getMessage());
            }

            @Override
            public void onFinish(ConvertedFiles ppt, ConversionInfo convertInfo) {
                mRoom.putScenes("/static", ppt.getScenes(), 0);
                mRoom.setScenePath("/static/1");
                logAction(convertInfo.toString());
            }

            @Override
            public void onProgress(Double progress, ConversionInfo convertInfo) {
                logAction(String.valueOf(progress));
            }
        });

        // ConvertV5
        ConverterV5.Builder builder = new ConverterV5.Builder();
        ConverterV5 converter = builder
                .setResource("https://white-cn-edge-doc-convert.oss-cn-hangzhou.aliyuncs.com/LightWaves.pdf")
                .setType(ConvertType.Static)
                .setScale(1.5)
                .setOutputFormat(ImageFormat.JPEG)
                .setSdkToken(demoAPI.getSdkToken())
                .setTaskUuid(null)
                .setTaskToken(null)
                .setCallback(new ConverterCallbacks() {
                    @Override
                    public void onProgress(Double progress, ConversionInfo convertInfo) {

                    }

                    @Override
                    public void onFinish(ConvertedFiles ppt, ConversionInfo convertInfo) {

                    }

                    @Override
                    public void onFailure(ConvertException e) {

                    }
                })
                .build();
        converter.startConvertTask();
    }*/

    /*public void dynamicConvert(MenuItem item) {
        Converter c = new Converter(this.token);
        c.startConvertTask("https://white-cn-edge-doc-convert.oss-cn-hangzhou.aliyuncs.com/-1/1.pptx", Converter.ConvertType.Dynamic, new ConverterCallbacks() {
            @Override
            public void onFailure(ConvertException e) {
                logAction(e.getMessage());
            }

            @Override
            public void onFinish(ConvertedFiles ppt, ConversionInfo convertInfo) {
                mRoom.putScenes("/dynamic", ppt.getScenes(), 0);
                mRoom.setScenePath("/dynamic/1");
                logAction(convertInfo.toString());
            }

            @Override
            public void onProgress(Double progress, ConversionInfo convertInfo) {
                logAction(String.valueOf(progress));
            }
        });

        // ConvertV5
        ConverterV5.Builder builder = new ConverterV5.Builder();
        ConverterV5 converter = builder
                .setResource("https://white-cn-edge-doc-convert.oss-cn-hangzhou.aliyuncs.com/-1/1.pptx")
                .setType(ConvertType.Dynamic)
                .setPreview(true)
                .setSdkToken(demoAPI.getSdkToken())
                .setTaskUuid(null)
                .setTaskToken(null)
                .setCallback(new ConverterCallbacks() {
                    @Override
                    public void onProgress(Double progress, ConversionInfo convertInfo) {

                    }

                    @Override
                    public void onFinish(ConvertedFiles ppt, ConversionInfo convertInfo) {

                    }

                    @Override
                    public void onFailure(ConvertException e) {

                    }
                })
                .build();
        converter.startConvertTask();
    }*/

    public void broadcast(MenuItem item) {
        logAction();
        mRoom.setViewMode(ViewMode.Broadcaster);
    }

    public void getBroadcastState(MenuItem item) {
        logAction();
        BroadcastState broadcastState = mRoom.getBroadcastState();
        showToast(broadcastState.getMode());
        logRoomInfo(gson.toJson(broadcastState));
    }

    public void moveCamera(MenuItem item) {
        logAction();
        CameraConfig config = new CameraConfig();
        config.setCenterX(100d);
        mRoom.moveCamera(config);
    }

    public void moveRectangle(MenuItem item) {
        logAction();
        RectangleConfig config = new RectangleConfig(200d, 400d);
        mRoom.moveCameraToContainer(config);
    }

    public void dispatchCustomEvent(MenuItem item) {
        logAction();
        HashMap<String, String> payload = new HashMap<>();
        payload.put("device", "android");

        mRoom.dispatchMagixEvent(new AkkoEvent(EVENT_NAME, payload));
    }

    public void cleanScene(MenuItem item) {
        logAction();
        mRoom.cleanScene(true);
    }

    public void insertNewScene(MenuItem item) {
        logAction();
        mRoom.putScenes(SCENE_DIR, new Scene[]{
                new Scene("page1")}, 0);
        mRoom.setScenePath(SCENE_DIR + "/page1");
    }

    public void insertPPT(MenuItem item) {
        logAction();
        mRoom.putScenes(SCENE_DIR, new Scene[]{
                new Scene("page2", new PptPage("https://white-pan.oss-cn-shanghai.aliyuncs.com/101/image/alin-rusu-1239275-unsplash_opt.jpg", 600d, 600d))
        }, 0);
        mRoom.setScenePath(SCENE_DIR + "/page2");
    }

    public void insertImage(MenuItem item) {
        mRoom.insertImage(new ImageInformationWithUrl(0d, 0d, 100d, 200d, "https://white-pan.oss-cn-shanghai.aliyuncs.com/40/image/mask.jpg"));
    }

    public void getScene(MenuItem item) {
        logAction();
        logAction(gson.toJson(mRoom.getScenes()));
    }

    public void getRoomPhase(MenuItem item) {
        logAction();
        logRoomInfo("RoomPhase: " + gson.toJson(mRoom.getRoomPhase()));
    }

    public void getRoomState(MenuItem item) {
        logAction();
        //获取房间状态，包含很多信息
        logRoomInfo("roomState: " + gson.toJson(mRoom.getRoomState()));
    }

    public void disconnect(MenuItem item) {

        //如果需要房间断开连接后回调
        mRoom.disconnect(new Promise<Object>() {
            @Override
            public void then(Object o) {
                logAction("disconnect success");
            }

            @Override
            public void catchEx(SDKError t) {

            }
        });

        //如果不需要回调，则直接断开连接即可
        //room.disconnect();
    }

    public void disableOperation(MenuItem item) {
        logAction();
        mRoom.disableOperations(true);
    }

    public void cancelDisableOperation(MenuItem item) {
        logAction();
        mRoom.disableOperations(false);
    }

    public void textArea(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        if (color == null) {
            color = new int[]{99, 99, 99};
        }
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.TEXT);
        memberState.setStrokeWidth(10);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void selector(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        memberState.setCurrentApplianceName(Appliance.SELECTOR);
        mRoom.setMemberState(memberState);
    }

    public void eraser(MenuItem item) {
        MemberState memberState = new MemberState();
        if (color == null) {
            color = new int[]{99, 99, 99};
        }
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.ERASER);
        memberState.setStrokeWidth(4);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void pencil(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        if (color == null) {
            color = new int[]{99, 99, 99};
        }
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.PENCIL);
        memberState.setStrokeWidth(10);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }


    public void rectangle(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        if (color == null) {
            color = new int[]{99, 99, 99};
        }
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.RECTANGLE);
        memberState.setStrokeWidth(10);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void colorRed(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        color = new int[]{255, 0, 0};
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.PENCIL);
        memberState.setStrokeWidth(4);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void colorBlue(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        color = new int[]{0, 255, 0};
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.PENCIL);
        memberState.setStrokeWidth(4);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void setPencil(){
        MemberState memberState = new MemberState();
        if (color == null) {
            color = new int[]{99, 99, 99};
        }
        memberState.setStrokeColor(color);
        memberState.setCurrentApplianceName(Appliance.PENCIL);
        memberState.setStrokeWidth(4);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void shape(MenuItem item) {
        logAction();
        MemberState memberState = new MemberState();
        if (color == null) {
            color = new int[]{200, 200, 200};
        }
        memberState.setStrokeColor(color);
        // defalut Shape Triangle
        memberState.setCurrentApplianceName(Appliance.SHAPE);
        // memberState.setShapeType(ShapeType.Rhombus);
        memberState.setStrokeWidth(4);
        memberState.setTextSize(10);
        mRoom.setMemberState(memberState);
    }

    public void convertPoint(MenuItem item) {
        //获取特定点，在白板内部的坐标点
        mRoom.convertToPointInWorld(0, 0, new Promise<Point>() {
            @Override
            public void then(Point point) {
                logRoomInfo(gson.toJson(point));
            }

            @Override
            public void catchEx(SDKError t) {
                Log.e(TAG, "convertToPointInWorld error " + t.getStackTrace().toString());
            }
        });
    }

    public void externalEvent(MenuItem item) {
        logAction();
    }

    public void zoomChange(MenuItem item) {
        CameraConfig cameraConfig = new CameraConfig();
        if (mRoom.getZoomScale() != 1) {
            cameraConfig.setScale(1d);
        } else {
            cameraConfig.setScale(5d);
        }
        mRoom.moveCamera(cameraConfig);
    }

    //endregion

    //region log
    void logRoomInfo(String str) {
        LogUtils.i(Thread.currentThread().getStackTrace()[3].getMethodName() + " " + str);
    }

    void logAction(String str) {
        LogUtils.i(Thread.currentThread().getStackTrace()[3].getMethodName() + " " + str);
    }

    void logAction() {
        LogUtils.i(Thread.currentThread().getStackTrace()[3].getMethodName());
    }

    void showToast(Object o) {
        Log.i("showToast", o.toString());
        Toast.makeText(this, o.toString(), Toast.LENGTH_SHORT).show();
    }
    //endregion
}
